/*
 * Created on Jun 25, 2010
 * Last modified May 30, 2013 by Eric T Dawson
 */
package org.reactome.cytoscape.v3.internal;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.cytoscape.view.model.CyNetworkView;
import org.cytoscape.view.model.View;
import org.cytoscape.model.*;


/**
 * This helper class is used to handle CyTable values.
 * @author wgm, Ported to 3.x by Eric T Dawson
 *
 */
public class CyAttributesHelper
{
    // List of clustering algorithms
    public static final String HOTNET_MODULE = "HOTNET_MODULE";
    public static final String MCL_ARRAY_CLUSTERING = "MCL_ARRAY_CLUSTERING";
    public static final String SPECTRA_PARTITION_CLUSTERING = "SPECTRAL_PARTITION_CLUSTERING";
    // List of data types
    public static final String MCL_ARRAY_DATA = "MCL_ARRAY_DATA";
    public static final String SAMPLE_MUATAION_DATA = "SAMPLE_MUATATION_DATA";
    // Key for storing FI network version
    private final String FI_NETWORK_VERSION = "Reactome_FI_Network_Version";
    private CyTableManager ctm;
    
    public CyAttributesHelper()
    {
    }
    
    public void storeDataSetType(CyNetworkView view,
                                 String type)
    {
	CyTable netTable = view.getModel().getDefaultNetworkTable();
	if (netTable.getColumn("DataSetType") == null)
	    netTable.createColumn("DataSetType", String.class, false);
	view.getModel().getDefaultNetworkTable().getRow(view.getModel()).set("DataSetType", type);
	if (netTable.getColumn("DataSetType") == null)
	    netTable.createColumn("IsReactomeFINetwork", String.class, false);
	view.getModel().getDefaultNetworkTable().getRow(view.getModel()).set("IsReactomeFINetwork", Boolean.TRUE);
	
    }
    
    public boolean isFINetwork(CyNetworkView view)
    {
	CyNetwork net = view.getModel();
	Boolean isFINetwork = net.getDefaultNetworkTable().getRow(net).get("IsReactomeFINetwork", Boolean.class);
	if (isFINetwork != null)
	    return isFINetwork;
	String dataSetType = net.getDefaultNetworkTable().getRow(net).get("DataSetType", String.class);
	if (dataSetType == null)
	           return false;
	       if (dataSetType.equals(MCL_ARRAY_CLUSTERING) ||
	           dataSetType.equals(SAMPLE_MUATAION_DATA))
	           return true;
	       return false;
    }
    
    public void storeFINetworkVersion(CyNetworkView view,
                                      String version)
    {
	CyTable netTable = view.getModel().getDefaultNetworkTable();
	if (netTable.getColumn(FI_NETWORK_VERSION) == null)
	    netTable.createColumn(FI_NETWORK_VERSION, String.class, false);
	netTable.getRow(view.getModel().getSUID()).set(FI_NETWORK_VERSION, version);
	
	
    }
    
    public String getStoredFINetworkVersion(CyNetworkView view)
    {
	CyTable netTable = view.getModel().getDefaultNetworkTable();
	String fiNetworkVersion = netTable.getRow(netTable).get(FI_NETWORK_VERSION, String.class);
        return fiNetworkVersion;
    }
                                            
    
    public String getDataSetType(CyNetworkView view)
    {
	CyTable netTable = view.getModel().getDefaultNetworkTable();
	return netTable.getRow(netTable).get("DataSetType", String.class);
    }
    
    public void storeMCLModuleToSampleToValue(CyNetworkView view,
                                              Map<Integer, Map<String, Double>> moduleToSampleToValue)
    {
	///////////////////////Does no create a map attribute; fix it.
        CyTable netTable = view.getModel().getDefaultNetworkTable();
        Map<String, String> moduleToText = new HashMap<String, String>();
        StringBuilder builder = new StringBuilder();
        for (Integer module : moduleToSampleToValue.keySet())
        {
            builder.setLength(0);
          Map<String, Double> sampleToValue = moduleToSampleToValue.get(module);
          for (String sample : sampleToValue.keySet())
          {
              Double value = sampleToValue.get(sample);
              builder.append(sample).append(":").append(value).append(",");
          }
          moduleToText.put(module + "", builder.toString());
        }
        netTable.getRow(netTable).set("moduleToSampleValue", moduleToText);
        
    }
    
    @SuppressWarnings("unchecked")
    public Map<Integer, Map<String, Double>> getMCLModuleToSampleToValue(CyNetworkView view)
    {
        //CyAttributes attributes = Cytoscape.getNetworkAttributes();
        //Map<String, String> moduleToText = attributes.getMapAttribute(view.getModel().getSUID(), 
                                                                      //"moduleToSampleToValue");
	CyTable netTable = view.getModel().getDefaultNetworkTable();
	Map<String, String> moduleToText = netTable.getRow(netTable).get("moduleToSampleValue", Map.class);
        Map<Integer, Map<String, Double>> moduleToSampleToValue = new HashMap<Integer, Map<String,Double>>();
        for (String module : moduleToText.keySet())
        {
            String text = moduleToText.get(module);
            String[] tokens = text.split(",");
            Map<String, Double> sampleToValue = new HashMap<String, Double>();
            moduleToSampleToValue.put(new Integer(module),
                                      sampleToValue);
            for (String token : tokens)
            {
                String[] tokens1 = token.split(":");
                sampleToValue.put(tokens1[0], new Double(tokens1[1]));
            }
        }
        return moduleToSampleToValue;
    }
    
    public void storeClusteringType(CyNetworkView view,
                                    String clusteringType)
    {
	CyTable netTable = view.getModel().getDefaultNetworkTable();
	netTable.getRow(netTable).set("Clustering_Type", clusteringType);
    }
    
    public String getClusteringType(CyNetworkView network) 
    {
	List<String> type = network.getModel().getDefaultNetworkTable().getColumn("Clustering_Type").getValues(String.class);
        return type.get(0);
    }
                                    
    
    /**
     * TODO: This method should be merged with loadEgeAttributes()
     * @param network
     * @param attributeName
     * @param edgeIdToValue
     */
    public void assignEdgeAttributes(CyNetworkView view,
                                      String attributeName, 
                                      Map<String, String> edgeIdToValue)
    {
	//This one does not have the proper type (id:value String:String pair). fix it.

	CyTable edgeTable =  view.getModel().getDefaultEdgeTable();
	for (Iterator<?> it = view.getModel().getEdgeList().iterator(); it.hasNext();)
	{
	    View<CyEdge> edgeView = (View<CyEdge>) it.next();
	    Long id = edgeView.getSUID();
            String value = edgeIdToValue.get(id);
            if (value != null)
                edgeTable.getRow(edgeView.getSUID()).set(attributeName, value);
        }
     }
    
    public void loadEdgeAttributes(CyNetworkView view,
                                   String attributeName,
                                   Map<String, ?> idToValue)
    {
        Class<?> type = guessAttributeType(idToValue);
        CyTable edgeTable =  view.getModel().getDefaultEdgeTable();
    	for (Iterator<?> it = view.getModel().getEdgeList().iterator(); it.hasNext();)
    	{
    	    View<CyEdge> edgeView = (View<CyEdge>) it.next();
                Long id = edgeView.getSUID();
                if (idToValue.containsKey(id))
                    setAttributeValue(attributeName, 
                            idToValue,
                            edgeTable,
                            id,
                            type);
        }
    }
    
    public void loadNodeAttributes(CyNetworkView view,
                                    String attributeName,
                                    Map<String, ?> idToValue)
    {
        Class<?> type = guessAttributeType(idToValue);
        CyTable nodeTable =  view.getModel().getDefaultNodeTable();
        for (Iterator<?> it = view.getModel().getNodeList().iterator(); it.hasNext();)
        {
            View<CyNode> nodeView = (View<CyNode>) it.next();
            Long id = nodeView.getSUID();
            if (idToValue.containsKey(id))
                setAttributeValue(attributeName, 
                        idToValue,
                        nodeTable,
                        id,
                        type);
        }
        //Cytoscape.firePropertyChange(Cytoscape.ATTRIBUTES_CHANGED, null, null);
    }
    
    private void setAttributeValue(String attributeName, 
                                   Map<String, ?> idToValue,
                                   CyTable table, 
                                   Long id,
                                   Class<?> type) {
        Object value = idToValue.get(id);
        if (value == null)
        {
            Object tmp = table.getRow(id.toString()).get(attributeName, type);
            if (tmp != null)
               table.getRow(id.toString()).set(attributeName, null);
            return; 	
        }
        //The following could probably be done with a one liner.
        if (type == Integer.class)
        {
            table.getRow(id).set(attributeName, (Integer) value);
        }
        else if (type == Double.class)
        {
            table.getRow(id).set(attributeName, (Double) value);
        }
        else if (type == String.class)
        {
            table.getRow(id).set(attributeName, (String) value);
        }
        else if (type == Boolean.class)
        {
            table.getRow(id).set(attributeName, (Boolean) value);
        }
    }
    
    private Class<?> guessAttributeType(Map<String, ?> idToValue) {
        for (Object key : idToValue.keySet()) {
            Object obj = idToValue.get(key);
            if (obj == null)
                continue;
            return obj.getClass();
        }
        return null;
    }
    
    //More giny dependencies; fix it.
//    public Map<String, Object> getNodeAttributeValues(CyNetwork network,
//                                                      String attributeName) {
//        CyAttributes attributes = Cytoscape.getNodeAttributes();
//        // Check if the attribute has been defined
//        String[] names = attributes.getAttributeNames();
//        boolean isFound = false;
//        for (String name : names) {
//            if (name.equals(attributeName)) {
//                isFound = true;
//                break;
//            }
//        }
//        if (!isFound)
//            return null;
//        Map<String, Object> idToValue = new HashMap<String, Object>();
//        for (Iterator<?> it = network.nodesIterator(); it.hasNext();) {
//            Node node = (Node) it.next();
//            Object value = attributes.getAttribute(node.getSUID(), attributeName);
//            if (value == null)
//                continue;
//            idToValue.put(node.getSUID(), 
//                          value);
//        }
//        return idToValue;
//    }
    
    public Map<String, Object> getNodeAttributeValues(CyNetwork network, String attributeName)
    {
	//fix it. May need to check all three default tables.
	CyTable nodeTable = network.getDefaultNodeTable();
	boolean isFound = false;
	if (nodeTable.getColumn(attributeName) != null)
	    isFound = true;
	if (!isFound)
	    return null;
	Map<String, Object> idToValue = new HashMap<String, Object>();
	for (Iterator<?> it = network.getNodeList().iterator(); it.hasNext();)
	{
	    CyNode node = (CyNode) it.next();
	    Object value = nodeTable.getRow(node.getSUID()).get(attributeName, Object.class);
	    if (value == null)
		continue;
	    idToValue.put(node.getSUID().toString(), value);
	}
	return idToValue;
	
    }
    public boolean hasEdgeAttributes (CyEdge edge,
	    				String attributeName)
    {
	CyTable table = edge.getSource().getNetworkPointer().getDefaultEdgeTable();
	//Because Object is the parent type, all primitives and references should be picked up.
	Object value = table.getRow(edge.getSUID()).get(attributeName, Object.class);
	return value != null;
    }
    public void deleteFIAttributes() {
	//Should delete all node attribute columns for a given local set of tables
	//There does not appear to be a global set of node attributes.
	//Make sure it is network based
	//Most likely not needed since attributes are stored locally and not globally
	//Make sure nothing is left post deleting network to make sure no memory leaks exists
	//then we can kill this function. fix it.
        Set<CyTable> nodeTables = ctm.getLocalTables(CyNode.class);
        for (CyTable table:nodeTables)
        {
            Set<String> names = CyTableUtil.getColumnNames(table);
            for (String name:names)
            {
        	table.deleteColumn(name);
            }
        }
        Set<CyTable> edgeTables = ctm.getLocalTables(CyEdge.class);
        for (CyTable table:edgeTables)
        {
            Set<String> names = CyTableUtil.getColumnNames(table);
            for (String name:names)
            {
        	table.deleteColumn(name);
            }
        }
        
        
	
    }
    
}
